package org.bdware.irp.client;

import com.google.gson.*;
import com.nimbusds.jose.jwk.JWK;
import org.apache.log4j.Logger;
import org.bdware.irp.exception.IrpClientException;
import org.bdware.irp.irplib.core.*;
import org.bdware.irp.irplib.util.EncoderUtils;
import org.bdware.irp.irplib.util.GlobalUtils;
import org.bdware.irp.stateinfo.StateInfoBase;

import org.bdware.irp.irplib.exception.IrpConnectException;
import org.bdware.irp.irpclient.IrpClientChannelGenerator;
import org.bdware.irp.irpclient.IrpClientChannel;
import org.bdware.irp.irpclient.IrpMessageCallback;

import java.net.URISyntaxException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class IrpClientImpl implements IrpClient, IrsClient {

    Logger logger = Logger.getLogger(IrpClientImpl.class);

    IrpClientChannel irpChannel;
    String serverURL = null;
    //PrintCallback pcb;
    ResponseCallback rcb;
    ResponseContainer container;
    String ClientID;
    JWK clientKeyPair;
    //String pre_doid = null;
    String address = null;

    public IrpClientImpl(JWK kp, String clientID, String LHSUrl){
        this.clientKeyPair = kp;
        this.ClientID = clientID;
        this.serverURL = LHSUrl;
        container = new ResponseContainer();
        rcb = new ResponseCallback(container);
    }

    public String resolveHandle(String handle) throws IrpClientException{
        IrpMessage req = HandleRequest.newResolveHandleRequest(handle, null,null);
        sendMessage(req, rcb);
        HandleResponse res = (HandleResponse)waitForResponse();
        JsonArray values = JsonParser.parseString(res.getHandleValuesAsJson()).getAsJsonArray();;
        JsonObject result = new JsonObject();
        result.addProperty("handle",res.getHandle());
        result.add("values",values);
        return result.toString();
    }

    @Override
    public StateInfoBase resolve(String doid) throws IrpClientException {
        IrpMessage req = IrpRequest.newIrsResolveRequest(doid, null);
        sendMessage(req, rcb);
        IrpResponse res = waitForResponse();
        logger.info(res.toString());
        if (res.result == "success"){
            String values = new Gson().toJson(res.getDoidValuesByMap());
            JsonObject resp = JsonParser.parseString(values).getAsJsonObject();
            StateInfoBase hr = new StateInfoBase();
            if(resp == null){
                throw new IrpClientException("resolve failed!");
            }
            hr.identifier = res.getDoid();
            hr.handleValues = resp;
            return hr;
        }else{
            return null;
        }

    }

    @Override
    public String register(StateInfoBase hr) throws IrpClientException {
        Map<String, String> map = hrToMap(hr);
        map.remove("identifier");
        IrpMessage req = IrpRequest.newIrsCreateDoidRequest(map, clientKeyPair);
        sendMessage(req, rcb);
        IrpResponse res = waitForResponse();
        if(res.result != "success"){
            logger.error("Register doid failed: " + res.getResponseMessage());
            throw new IrpClientException("register failed!");
        }
        return res.getDoid();
    }

    @Override
    public String reRegister(StateInfoBase hr) throws IrpClientException {
        if(hr.identifier == null){
            logger.warn("handle need to be set.");
        }
        Map<String, String> map = hrToMap(hr);
        IrpMessage req = IrpRequest.newIrsUpdateDoidRequest(hr.identifier, map, clientKeyPair);
        sendMessage(req, rcb);
        IrpResponse res = waitForResponse();

        if(res.result != "success"){
            logger.error("Reregister doid failed: " + res.getResponseMessage());
            throw new IrpClientException("reregister failed!");
        }
        return res.getDoid();
    }

    @Override
    public String unRegister(String handle) {
        if(handle == null){
            logger.warn("handle need to be set.");
        }
        IrpMessage req = IrpRequest.newIrsDeleteDoidRequest(handle, clientKeyPair);
        sendMessage(req, rcb);
        IrpResponse res = waitForResponse();

        if(res.result != "success"){
            logger.error("Delete doid failed: " + res.getResponseMessage());
        }
        return res.result;
    }

    @Override
    public List<String> batchRegister(StateInfoBase hr, int count) throws IrpClientException {
        Map<String, String> map = hrToMap(hr);
        map.remove("identifier");
        IrpMessage req = IrpRequest.newIrsBatchCreateDoidRequest(map, count, clientKeyPair);
        sendMessage(req, rcb);
        IrpResponse res = waitForResponse();
        if(res.result != "success"){
            logger.error("Register doid failed: " + res.getResponseMessage());
            throw new IrpClientException("register failed!");
        }
        return res.getDoidList();
    }

    @Override
    public boolean verifyIrsServer(){
        if(clientKeyPair.getKeyID() == null){
            logger.warn("pre_doid need to be set in the jwk sk string to verify the irs server!");
            return false;
        }
        IrpMessage req = IrpRequest.newVerityIrsServerRequest(clientKeyPair, address);
        sendMessage(req, rcb);
        IrpResponse res = waitForResponse();

        if(res.result != "success"){
            logger.error("Verify the IRS Server from GRS failed: " + res.getResponseMessage());
            return false;
        }
        return true;
    }

    @Override
    public StateInfoBase resolvePrefix(String doid) throws IrpClientException {
        IrpMessage req = IrpRequest.newGrsResolveRequest(doid, null);
        sendMessage(req, rcb);
        IrpResponse res = waitForResponse();
        logger.info(res.toString());
        if (res.result == "success"){
            String values = new Gson().toJson(res.getDoidValuesByMap());
            JsonObject resp = JsonParser.parseString(values).getAsJsonObject();
            StateInfoBase hr = new StateInfoBase();
            if(resp == null){
                throw new IrpClientException("resolve failed!");
            }
            hr.identifier = res.getDoid();
            hr.handleValues = resp;
            return hr;
        }else{
            return null;
        }
    }

    @Override
    public boolean allocatePrefix(StateInfoBase stateInfoBase) throws IrpClientException {
        logger.info(new Gson().toJson(stateInfoBase));
        IrpMessage req = IrpRequest.newGrsCreateRequest(stateInfoBase.getIdentifier(), hrToMap(stateInfoBase), null);
        sendMessage(req, rcb);
        IrpResponse res = waitForResponse();
        logger.info(res.toString());
        if (res.result == "success"){
            return true;
        }else{
            return false;
        }
    }

    public void setAddress(String address){this.address = address;}

    private HashMap<String,String> hrToMap(StateInfoBase handleRecord){
        HashMap<String,String> hrMap = new HashMap<>();
        for(String keys:handleRecord.handleValues.keySet()){
            logger.info(keys);
            if(handleRecord.handleValues.get(keys)!=null && !handleRecord.handleValues.get(keys).isJsonNull())
                hrMap.put(keys,handleRecord.handleValues.get(keys).getAsString());
        }
        String className = handleRecord.getClass().getSimpleName();
        switch (className){
            case "DoStateInfo":
                hrMap.put("hrType","do");
                break;
            case "DoipServiceStateInfo":
                hrMap.put("hrType", "org/bdware/doip");
                break;
            case "UserStateInfo":
                hrMap.put("hrType","dou");
                break;
            default:
                logger.error("use specific handle record class instead.");
                break;
        }
        return hrMap;
    }

    IrpResponse waitForResponse(){
        container.response = null;
        synchronized (rcb) {
            try {
                rcb.wait(5000L);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        if(container.response == null) {
            container.response = IrpResponse.newErrorResponse(
                    IrpMessageCode.OC_RESERVED,
                    IrpMessageCode.RC_ERROR,
                    "Server response timeout!");
        }
        return container.response;
    }

    public void close(){
        irpChannel.close();
        irpChannel = null;
    };

    public void connect(String url){
        try {
            irpChannel = IrpClientChannelGenerator.createIrpClientChannel(url);
            if(irpChannel == null) return;
            irpChannel.connect(url);
            serverURL = url;
        } catch (URISyntaxException e) {
            e.printStackTrace();
        }
    }

    public void reconnect() throws IrpConnectException {
        if(serverURL == null) throw (new IrpConnectException("target URL not set, use .connect(url) first"));
        if(irpChannel == null) irpChannel = IrpClientChannelGenerator.createIrpClientChannel(serverURL);
        if(irpChannel == null) return;
        try {
            irpChannel.connect(serverURL);
        }catch (URISyntaxException e) {
            e.printStackTrace();
        }
    }

    public boolean isConnected(){
        return irpChannel != null && irpChannel.isConnected();
    }

    public void sendMessage(IrpMessage msg, IrpMessageCallback cb){
        connect(serverURL);
        if(irpChannel == null || !irpChannel.isConnected()){
            logger.warn("channel not connect yet!");
            return;
        }
        //msg.setRecipientID(recipientID);
        irpChannel.sendMessage(msg,cb);
    };

/*    class PrintCallback implements IrpMessageCallback {
        @Override
        public void onResult(IrpMessage msg) {
            logger.info("message header: " + msg.header.opCode);
            IrpResponse res = (IrpResponse)msg;
            System.out.println("result： " + GlobalUtils.decodeString(res.doid));
            if(res.doid != null)
                logger.info("message body: " + GlobalUtils.decodeString(res.doid));
        }
    }*/

    class ResponseCallback implements IrpMessageCallback {
        ResponseContainer responseContainer;

        public ResponseCallback(ResponseContainer container){
            responseContainer = container;
        }
        @Override
        public synchronized void onResult(IrpMessage msg) {
            logger.info("<callback>message header: " + msg.header.opCode);
            responseContainer.response = (IrpResponse)msg;
            this.notifyAll();
        }
    }

    static class ResponseContainer{
        IrpResponse response;
    }
}
